/**
 * Copyright (c) 2018-2022, NXOS Development Team
 * SPDX-License-Identifier: Apache-2.0
 * 
 * Contains: Thread for NXOS
 * 
 * Change Logs:
 * Date           Author            Notes
 * 2021-11-7      JasonHu           Init
 */

#define NX_LOG_NAME "Thread"
#define NX_LOG_LEVEL NX_LOG_WARNING
#include <base/log.h>
#include <base/debug.h>
#include <base/irq.h>

#include <base/thread.h>
#include <base/thread_id.h>
#include <base/sched.h>
#include <base/mutex.h>
#include <base/smp.h>
#include <base/context.h>
#include <base/malloc.h>
#include <base/page.h>
#include <base/string.h>
#include <base/timer.h>
#include <base/hooks.h>

NX_ThreadManager gThreadManagerObject;

NX_PRIVATE void ThreadEntry(void)
{
    NX_Thread *self = NX_ThreadSelf();
    NX_IRQ_Enable();
    self->handler(self->threadArg);
    NX_ThreadExit(0);
    NX_PANIC("ThreadEntry");
}

NX_PRIVATE NX_Error ThreadInit(NX_Thread *thread, 
    const char *name,
    NX_ThreadHandler handler, void *arg,
    NX_U8 *stack, NX_Size stackSize,
    NX_U32 priority)
{
    int i;

    if (thread == NX_NULL || name == NX_NULL || handler == NX_NULL || stack == NX_NULL || !stackSize)
    {
        return NX_EINVAL;
    }

    NX_ListInit(&thread->list);
    NX_ListInit(&thread->globalList);
    NX_ListInit(&thread->exitList);
    NX_ListInit(&thread->processList);
    NX_ListInit(&thread->blockList);

    NX_StrCopy(thread->name, name);
    thread->tid = NX_ThreadIdAlloc();
    if (thread->tid < 0)
    {
        NX_LOG_E("No enough thread id.");
        return NX_ENORES;
    }
    NX_ThreadSetState(thread, NX_THREAD_INIT);
    thread->handler = handler;
    thread->userHandler = NX_NULL;
    thread->threadArg = arg;
    thread->timeslice = 3;
    thread->elapsedTicks = 0;
    thread->ticks = thread->timeslice;
    thread->fixedPriority = priority;
    thread->priority = priority;
    thread->needSched = 0;
    thread->stackBase = stack;
    thread->stackSize = stackSize;
    thread->stack = thread->stackBase + stackSize - sizeof(NX_UArch);
    thread->stack = NX_ContextInit(ThreadEntry, (void *)NX_NULL, NX_NULL, thread->stack);
    thread->userStackBase = NX_NULL;
    thread->userStackSize = 0;
    thread->flags = 0;

    thread->onCore = NX_MULTI_CORES_NR; /* not on any core */
    thread->coreAffinity = NX_MULTI_CORES_NR; /* no core affinity */

    thread->resource.sleepTimer = NX_NULL;
    thread->resource.process = NX_NULL;
    thread->resource.fileTable = NX_FileTableGetDefault();
    thread->resource.hub = NX_NULL;
    thread->resource.ipcServer = NX_NULL;
    thread->resource.activeChannel = NX_NULL;
    thread->resource.ipcChannel = NX_NULL;
    thread->resource.exitCode = 0;
    thread->resource.waitExitCode = 0;
    NX_SemaphoreInit(&thread->resource.waiterSem, 0);
    thread->resource.tls = NX_NULL;
    NX_SignalTableInit(&thread->resource.signals);
    NX_ThreadUsageInit(&thread->resource.usageInfo);
    NX_AtomicSet(&thread->resource.syscallDepth, 0);

    NX_AtomicSet(&thread->resource.lockStackTop, NX_THREAD_LOCK_CHAINS_NR);
    for (i = 0; i < NX_THREAD_LOCK_CHAINS_NR; i++)
    {
        thread->resource.lockStack[i] = NX_NULL;
    }

    NX_SpinInit(&thread->lock);
    return NX_EOK;
}

NX_Error NX_ThreadPushLock(NX_Thread * thread, NX_Mutex * lock)
{
    if (!thread || !lock)
    {
        return NX_EINVAL;
    }
    if (NX_AtomicGet(&thread->resource.lockStackTop) <= 0)
    {
        return NX_ENORES;
    }
    NX_AtomicDec(&thread->resource.lockStackTop);
    thread->resource.lockStack[NX_AtomicGet(&thread->resource.lockStackTop)] = lock;
    return NX_EOK;
}

NX_Mutex * NX_ThreadPopLock(NX_Thread * thread)
{
    NX_Mutex * mutex;
    if (!thread)
    {
        return NX_NULL;
    }
    if (NX_AtomicGet(&thread->resource.lockStackTop) >= NX_THREAD_LOCK_CHAINS_NR)
    {
        return NX_NULL;
    }
    mutex = thread->resource.lockStack[NX_AtomicGet(&thread->resource.lockStackTop)];
    thread->resource.lockStack[NX_AtomicGet(&thread->resource.lockStackTop)] = NX_NULL;
    NX_AtomicInc(&thread->resource.lockStackTop);
    return mutex;
}

NX_PRIVATE NX_Error ThreadDeInit(NX_Thread *thread)
{
    if (thread == NX_NULL)
    {
        return NX_EINVAL;
    }
    if (thread->stackBase == NX_NULL)
    {
        return NX_EFAULT;
    }
    NX_ThreadIdFree(thread->tid);    
    return NX_EOK;
}

NX_Thread *NX_ThreadCreate(const char *name, NX_ThreadHandler handler, void *arg, NX_U32 priority)
{
    if (!name || !handler || priority >= NX_THREAD_MAX_PRIORITY_NR)
    {
        return NX_NULL;
    }

    NX_Thread *thread = (NX_Thread *)NX_MemAlloc(sizeof(NX_Thread));
    if (thread == NX_NULL)
    {
        return NX_NULL;
    }
    NX_U8 *stack = NX_MemAlloc(NX_THREAD_STACK_SIZE_DEFAULT);
    if (stack == NX_NULL)
    {
        NX_MemFree(thread);
        return NX_NULL;
    }
    if (ThreadInit(thread, name, handler, arg, stack, NX_THREAD_STACK_SIZE_DEFAULT, priority) != NX_EOK)
    {
        NX_MemFree(stack);
        NX_MemFree(thread);
        return NX_NULL;
    }
    return thread;
}

NX_Error NX_ThreadDestroy(NX_Thread *thread)
{
    if (thread == NX_NULL)
    {
        return NX_EINVAL;
    }
    NX_U8 *stackBase = thread->stackBase;
    if (stackBase == NX_NULL)
    {
        return NX_EFAULT;
    }

    NX_Error err = ThreadDeInit(thread);
    if (err != NX_EOK)
    {
        return err;
    }

    NX_MemFree(stackBase);
    NX_MemFree(thread);
    return NX_EOK;
}

NX_PRIVATE void NX_ThreadAddToGlobalPendingList(NX_Thread *thread, int flags)
{
    if (flags & NX_SCHED_HEAD)
    {
        NX_ListAdd(&thread->list, &gThreadManagerObject.pendingList);
    }
    else
    {
        NX_ListAddTail(&thread->list, &gThreadManagerObject.pendingList);
    }
    NX_AtomicInc(&gThreadManagerObject.pendingThreadCount);
}

NX_PRIVATE void NX_ThreadDelFromGlobalPendingList(NX_Thread *thread)
{
    NX_ListDel(&thread->list);
    NX_AtomicDec(&gThreadManagerObject.pendingThreadCount);
}

void NX_ThreadReadyRunLocked(NX_Thread *thread, int flags)
{
    NX_ThreadSetState(thread, NX_THREAD_READY);

    NX_POST_THREAD_HOOK(thread);

    if (thread->onCore < NX_MULTI_CORES_NR) /* add to core pending list */
    {
        NX_SMP_EnqueueThreadIrqDisabled(thread->onCore, thread, flags);
    }
    else /* add to global pending list */
    {
        NX_ThreadAddToGlobalPendingList(thread, flags);
    }
}

void NX_ThreadReadyRunUnlocked(NX_Thread *thread, int flags)
{
    NX_UArch level;
    NX_Thread * self = NX_ThreadSelf();
    self->flags |= NX_THREAD_FLAG_UNINTERRUPTABLE;
    NX_SpinLockIRQ(&gThreadManagerObject.lock, &level);
    self->flags &= ~NX_THREAD_FLAG_UNINTERRUPTABLE;

    NX_ThreadReadyRunLocked(thread, flags);
    
    NX_SpinUnlockIRQ(&gThreadManagerObject.lock, level);
}

void NX_ThreadUnreadyRunLocked(NX_Thread *thread)
{
    if (thread->onCore < NX_MULTI_CORES_NR) /* add to core pending list */
    {
        NX_SMP_DequeueThreadIrqDisabled(thread->onCore, thread);
    }
    else /* add to global pending list */
    {
        NX_ThreadDelFromGlobalPendingList(thread);
    }
}

void NX_ThreadUnreadyRun(NX_Thread *thread)
{
    NX_ASSERT(thread->state != NX_THREAD_READY);

    if (thread->onCore < NX_MULTI_CORES_NR) /* add to core pending list */
    {        
        NX_SMP_DequeueThread(thread->onCore, thread);
    }
    else /* add to global pending list */
    {
        NX_ThreadDequeuePendingList(thread);
    }
}

NX_Error NX_ThreadStart(NX_Thread *thread)
{
    if (thread == NX_NULL)
    {
        return NX_EINVAL;
    }

    if (thread->state != NX_THREAD_INIT)
    {
        NX_LOG_E("thread %d/%s start state must be NX_THREAD_INIT", thread->tid, thread->name);
        return NX_EPERM;        
    }

    NX_UArch level;
    if (NX_SpinLockIRQ(&gThreadManagerObject.lock, &level) != NX_EOK)
    {
        return NX_EINTR;
    }

    NX_ThreadEnququeGlobalListUnlocked(thread);

    /* add to ready list */
    NX_ThreadReadyRunLocked(thread, NX_SCHED_TAIL);
    
    NX_SpinUnlockIRQ(&gThreadManagerObject.lock, level);
    return NX_EOK;
}

void NX_ThreadYield(void)
{
    NX_SchedYield();
}

NX_Error NX_ThreadTerminate(NX_Thread *thread, NX_U32 exitCode)
{
    if (thread == NX_NULL)
    {
        return NX_EINVAL;
    }
    if (thread->state == NX_THREAD_INIT || thread->state == NX_THREAD_EXIT)
    {
        return NX_EPERM;
    }

    thread->resource.exitCode = exitCode;
    return NX_SignalSend(thread->tid, NX_SIGNAL_TERMINATE, NX_NULL, NX_NULL);
}

NX_PRIVATE void ThreadExitNotify(NX_Thread * thread)
{
    NX_Thread * waiterThread;

    NX_ListForEachEntry(waiterThread, &thread->resource.waiterSem.semWaitList, blockList)
    {
        waiterThread->resource.waitExitCode = thread->resource.exitCode;
    }

    /* wakeup waiter */
    NX_SemaphoreSignalAll(&thread->resource.waiterSem);
}

/**
 * release resource the thread hold.
 */
NX_PRIVATE void ThreadReleaseResouce(NX_Thread *thread)
{
    /* NOTE: add other resource here. */
    if (thread->resource.sleepTimer != NX_NULL)
    {
        NX_TimerStop(thread->resource.sleepTimer);
        thread->resource.sleepTimer = NX_NULL;
    }

    /* thread exit notify */
    ThreadExitNotify(thread);

    /* exit thread signals */
    NX_SignalTableExit(&thread->resource.signals);

    /* thread had bind on process */
    if (thread->resource.process != NX_NULL)
    {
        NX_ThreadExitProcess(thread, thread->resource.process);
    }

    /* pop all mutex lock */
    NX_Mutex * lock;
    while ((lock = NX_ThreadPopLock(thread)) != NX_NULL)
    {
        NX_MUTEX_UNLOCK_OK(lock); /* unlock the lock */
    }

    /* exit channel resource */
    if (thread->resource.ipcChannel)
    {
        thread->resource.ipcChannel->state = NX_IPC_CHANNEL_CLOSE;
    }
}

void NX_ThreadExit(NX_U32 exitCode)
{
    /* free thread resource */
    NX_Thread *thread = NX_ThreadSelf();

    NX_LOG_D("thread %s/%d exit", thread->name, thread->tid);

    thread->resource.exitCode = exitCode;

    /* release the resource here that not the must for a thread! */
    ThreadReleaseResouce(thread);

    NX_UArch level;
    thread->flags |= NX_THREAD_FLAG_UNINTERRUPTABLE;
    NX_SpinLockIRQ(&gThreadManagerObject.lock, &level);
    thread->flags &= ~NX_THREAD_FLAG_UNINTERRUPTABLE;

    NX_ThreadDeququeGlobalListUnlocked(thread);

    NX_SpinUnlockIRQ(&gThreadManagerObject.lock, level);
    
    NX_SchedExit();
    NX_PANIC("Thread Exit should never arrive here!");
}

NX_Thread *NX_ThreadSelf(void)
{
    NX_Thread *cur = NX_SMP_GetRunning();
    return cur;
}

/**
 * must called with interrupt disabled.
 */
NX_Error NX_ThreadBlockLockedIRQ(NX_Thread *thread, NX_Spin *lock, NX_UArch irqLevel)
{
    if (thread == NX_NULL)
    {
        return NX_EINVAL;
    }

    if (thread->state == NX_THREAD_READY)
    {   /* remove from ready list */
        NX_ThreadSetState(thread, NX_THREAD_BLOCKED);

        NX_POST_THREAD_HOOK(thread);

        NX_ThreadUnreadyRun(thread);

        if (lock)
        {
            NX_SpinUnlockIRQ(lock, irqLevel);
        }
        else
        {
            NX_IRQ_RestoreLevel(irqLevel);
        }
    }
    else if (thread->state == NX_THREAD_RUNNING)
    {   /* set as block, then sched */
        NX_ThreadSetState(thread, NX_THREAD_BLOCKED);

        NX_POST_THREAD_HOOK(thread);

        NX_SchedLockedIRQ(irqLevel, lock, NX_NULL);
        
        if (NX_ThreadCheckInterrupt(thread))
        {
            return NX_EINTR;
        }
    }
    
    return NX_EOK;
}

/**
 * must called when interrupt disabled
 */
NX_Error NX_ThreadBlockInterruptDisabled(NX_Thread *thread, NX_UArch irqLevel)
{
    return NX_ThreadBlockLockedIRQ(thread, NX_NULL, irqLevel);
}

NX_Error NX_ThreadBlock(NX_Thread *thread)
{
    NX_UArch level = NX_IRQ_SaveLevel();
    return NX_ThreadBlockLockedIRQ(thread, NX_NULL, level);
}

/**
 * wakeup a thread, must called interrupt disabled
 */
NX_Error NX_ThreadUnblock(NX_Thread *thread)
{
    if (thread == NX_NULL)
    {
        return NX_EINVAL;
    }

    /* if thread in sleep, then wakeup it */
    if (thread->state == NX_THREAD_BLOCKED)
    {
        NX_ThreadReadyRunUnlocked(thread, NX_SCHED_HEAD);
        return NX_EOK;
    }

    return NX_EBUSY;
}

NX_PRIVATE NX_Bool TimerThreadSleepTimeout(NX_Timer *timer, void *arg)
{
    NX_Thread *thread = (NX_Thread *)arg; /* the thread wait for timeout  */

    thread->resource.sleepTimer = NX_NULL; /* cleanup sleep timer */

    if (NX_ThreadUnblock(thread) != NX_EOK)
    {
        NX_LOG_E("Wakeup thread:%s/%d failed!", thread->name, thread->tid);
    }
    
    return NX_True;
}

/* if thread sleep less equal than 2ms, use delay instead */
#define THREAD_SLEEP_TIMEOUT_THRESHOLD 2

NX_Error NX_ThreadSleep(NX_UArch microseconds)
{
    if (microseconds == 0)
    {
        return NX_EINVAL;
    }
    if (microseconds <= THREAD_SLEEP_TIMEOUT_THRESHOLD)
    {
        return NX_ClockTickDelayMillisecond(microseconds);
    }

    NX_Timer sleepTimer;
    NX_Error err;

    NX_UArch irqLevel = NX_IRQ_SaveLevel();
    NX_Thread *self = NX_ThreadSelf();

    /* check signal for thread exit */
    if (NX_ThreadCheckInterrupt(self))
    {
        NX_IRQ_RestoreLevel(irqLevel);
        return NX_EINTR;
    }

    err = NX_TimerInit(&sleepTimer, microseconds, TimerThreadSleepTimeout, (void *)self, NX_TIMER_ONESHOT);
    if (err != NX_EOK)
    {
        NX_IRQ_RestoreLevel(irqLevel);
        return err;
    }

    self->resource.sleepTimer = &sleepTimer;

    NX_TimerStart(self->resource.sleepTimer);

    /* set thread as sleep state */
    err = NX_ThreadBlockInterruptDisabled(self, irqLevel);

    /* if sleep timer always here, it means that the thread was interrupted! */
    if (self->resource.sleepTimer != NX_NULL || err != NX_EOK)
    {
        /* timer not stop now */
        NX_TimerStop(self->resource.sleepTimer);
        self->resource.sleepTimer = NX_NULL;

        return NX_EINTR; /* interrupted */
    }
    return NX_EOK;
}

NX_Error NX_ThreadSetAffinity(NX_Thread *thread, NX_UArch coreId)
{
    if (thread == NX_NULL || coreId >= NX_MULTI_CORES_NR)
    {
        return NX_EINVAL;
    }
    NX_UArch level;
    if (NX_SpinLockIRQ(&thread->lock, &level) != NX_EOK)
    {
        return NX_EINTR;
    }
    thread->coreAffinity = coreId;
    thread->onCore = coreId;
    NX_SpinUnlockIRQ(&thread->lock, level);
    return NX_EOK;
}

void NX_ThreadEnqueuePendingList(NX_Thread *thread)
{
    NX_UArch level;
    NX_Thread * self = NX_ThreadSelf();
    self->flags |= NX_THREAD_FLAG_UNINTERRUPTABLE;
    NX_SpinLockIRQ(&gThreadManagerObject.lock, &level);
    self->flags &= ~NX_THREAD_FLAG_UNINTERRUPTABLE;
    NX_ThreadAddToGlobalPendingList(thread, NX_SCHED_HEAD);
    NX_SpinUnlockIRQ(&gThreadManagerObject.lock, level);
}

void NX_ThreadDequeuePendingList(NX_Thread *thread)
{
    NX_UArch level;
    NX_Thread * self = NX_ThreadSelf();
    self->flags |= NX_THREAD_FLAG_UNINTERRUPTABLE;
    NX_SpinLockIRQ(&gThreadManagerObject.lock, &level);
    self->flags &= ~NX_THREAD_FLAG_UNINTERRUPTABLE;
    NX_ThreadDelFromGlobalPendingList(thread);
    NX_SpinUnlockIRQ(&gThreadManagerObject.lock, level);
}

NX_Thread *NX_ThreadPickPendingList(void)
{
    NX_Thread *thread;
    NX_SpinLock(&gThreadManagerObject.lock);
    thread = NX_ListFirstEntryOrNULL(&gThreadManagerObject.pendingList, NX_Thread, list);
    if (thread != NX_NULL)
    {
        NX_ListDel(&thread->list);
        NX_AtomicDec(&gThreadManagerObject.pendingThreadCount);
    }
    NX_SpinUnlock(&gThreadManagerObject.lock);
    return thread;
}

void NX_ThreadEnququeExitList(NX_Thread *thread)
{
    NX_UArch level;
    NX_Thread * self = NX_ThreadSelf();

    /**
     * enqueue exit list
     */
    self->flags |= NX_THREAD_FLAG_UNINTERRUPTABLE;
    NX_SpinLockIRQ(&gThreadManagerObject.exitLock, &level);
    self->flags &= ~NX_THREAD_FLAG_UNINTERRUPTABLE;
    NX_ASSERT(!NX_ListFind(&thread->exitList, &gThreadManagerObject.exitList));
    NX_ListAdd(&thread->exitList, &gThreadManagerObject.exitList);
    NX_SpinUnlockIRQ(&gThreadManagerObject.exitLock, level);
    
    /**
     * signal on notify sem
     */
    NX_SemaphoreSignal(&gThreadManagerObject.exitNotify);
}

NX_Thread *NX_ThreadDeququeExitList(void)
{
    NX_Thread *thread;
    NX_UArch level;
    NX_Thread * self = NX_ThreadSelf();

    /**
     * wait on notify sem
     */
    NX_SemaphoreWait(&gThreadManagerObject.exitNotify);

    /**
     * remove exit thread from exit list
     */
    self->flags |= NX_THREAD_FLAG_UNINTERRUPTABLE;
    NX_SpinLockIRQ(&gThreadManagerObject.exitLock, &level);
    self->flags &= ~NX_THREAD_FLAG_UNINTERRUPTABLE;
    thread = NX_ListFirstEntryOrNULL(&gThreadManagerObject.exitList, NX_Thread, exitList);
    if (thread != NX_NULL)
    {
        NX_ListDelInit(&thread->exitList);
        NX_ASSERT(!NX_ListFind(&thread->exitList, &gThreadManagerObject.exitList));
    }
    NX_SpinUnlockIRQ(&gThreadManagerObject.exitLock, level);
    return thread;
}

NX_Thread *NX_ThreadFindById(NX_U32 tid)
{
    NX_Thread *thread = NX_NULL, *find = NX_NULL;
    NX_UArch level;

    if (NX_SpinLockIRQ(&gThreadManagerObject.lock, &level) != NX_EOK)
    {
        return NX_NULL;
    }

    NX_ListForEachEntry (thread, &gThreadManagerObject.globalList, globalList)
    {
        if (thread->tid == tid)
        {
            find = thread;
            break;
        }
    }

    NX_SpinUnlockIRQ(&gThreadManagerObject.lock, level);
    return find;
}

NX_Error NX_ThreadWalk(NX_ThreadWalkHandler handler, void * arg)
{
    NX_Thread *thread;
    NX_UArch level;
    NX_Error err = NX_EOK;

    if (!handler)
    {
        return NX_EINVAL;
    }

    if (NX_SpinLockIRQ(&gThreadManagerObject.lock, &level) != NX_EOK)
    {
        return NX_EINTR;
    }

    NX_ListForEachEntry (thread, &gThreadManagerObject.globalList, globalList)
    {
        if ((err = handler(thread, arg)) != NX_EOK)
        {
            break;
        }
    }

    NX_SpinUnlockIRQ(&gThreadManagerObject.lock, level);

    return err;
}

NX_Error NX_ThreadWait(NX_Thread * thread, NX_U32 *exitCode)
{
    NX_Thread * self;
    NX_Error err;
    
    if (thread == NX_NULL)
    {
        return NX_ENOSRCH;
    }

    self = NX_ThreadSelf();

    if (thread == self)
    {
        NX_LOG_E("thread %s/%d wait self!", thread->name, thread->tid);
        return NX_EINVAL;
    }

    /* wait on waiters sem */
    if ((err = NX_SemaphoreWait(&thread->resource.waiterSem)) != NX_EOK)
    {
        return err;
    }
    
    if (exitCode)
    {
        *exitCode = self->resource.waitExitCode;
    }

    return NX_EOK;
}

NX_Error NX_ThreadChangePriority(NX_Thread *thread, NX_U32 priority)
{
    NX_UArch level;
    NX_Error err;

    if (thread == NX_NULL || NX_THREAD_IS_INVALID_PRIORITY(priority))
    {
        return NX_EINVAL;
    }
    
    err = NX_SpinLockIRQ(&gThreadManagerObject.lock, &level);
    if (err != NX_EOK)
    {
        return err;
    }

    /**
     * thread on ready list, need remove from list
     */
    if (thread->state == NX_THREAD_READY)
    {
        /* remove from ready list */
        NX_SMP_DequeueThreadIrqDisabled(thread->onCore, thread);

        thread->priority = priority;
        thread->fixedPriority = priority;

        NX_SMP_EnqueueThreadIrqDisabled(thread->onCore, thread, NX_SCHED_HEAD);
    }
    else
    {
        thread->priority = priority;
        thread->fixedPriority = priority;
    }
    err = NX_SpinUnlockIRQ(&gThreadManagerObject.lock, level);
    NX_ASSERT(err == NX_EOK);
    return NX_EOK;
}

void NX_ThreadManagerInit(void)
{
    NX_AtomicSet(&gThreadManagerObject.averageThreadThreshold, 0);
    NX_AtomicSet(&gThreadManagerObject.activeThreadCount, 0);
    NX_AtomicSet(&gThreadManagerObject.pendingThreadCount, 0);
    NX_ListInit(&gThreadManagerObject.exitList);
    NX_ListInit(&gThreadManagerObject.globalList);
    NX_ListInit(&gThreadManagerObject.pendingList);
    
    NX_SpinInit(&gThreadManagerObject.lock);
    NX_SpinInit(&gThreadManagerObject.exitLock);

    NX_SemaphoreInit(&gThreadManagerObject.exitNotify, 0);
}

NX_IMPORT void NX_ThreadInitIdle(void);
NX_IMPORT void NX_ThreadInitDeamon(void);

void NX_ThreadsInit(void)
{
    NX_ThreadsInitID();
    NX_ThreadManagerInit();
    NX_ThreadInitIdle();
    NX_ThreadInitDeamon();
}
